#include "Common.h"
#include "HttpClient.h"
#include <windows.h>
#include <winhttp.h>
#include <sstream>

unsigned int __stdcall HttpClient::HttpClientHandle(void *param)
{
	HttpClient* client = (HttpClient*)param;
	RequestEntry entry;

	while (!client->CheckThread())
	{
		if (!client->mPendingQueue.Pop(entry))
		{
			Sleep(100);
			continue;
		}

		client->Execute(entry.Request);
		client->mFinishQueue.Push(entry);
	}

	return 0;
}

HttpClient::HttpClient(const std::wstring& URL, int port, const std::wstring& session)
	: mURL(URL)
	, mSession(session)
	, mPort(port)
{
}

HttpClient::~HttpClient()
{
}

bool HttpClient::Init()
{
	mThread.Start(HttpClientHandle, this);
	return true;
}

bool HttpClient::Update()
{
	RequestEntry entry;
	while (mFinishQueue.Pop(entry))
	{
		if (entry.Callback)
			entry.Callback(entry.Request->getResponse());

		if (entry.AutoDelete)
			delete entry.Request;
	}

	return true;
}

void HttpClient::Destory()
{
	mThread.Final();
	
	RequestEntry entry;
	while (mFinishQueue.Pop(entry))
		if (entry.AutoDelete) delete entry.Request;
	while (mPendingQueue.Pop(entry))
		if (entry.AutoDelete) delete entry.Request;
}

bool HttpClient::PushRequest(HttpRequest* request, ResponseCallback callback, bool autoDelete)
{
	if (CheckThread())
	{
		if (autoDelete)
			delete request;
		return false;
	}

	mPendingQueue.Push(RequestEntry(request, callback, autoDelete));
	return true;
}

bool HttpClient::Execute(const std::wstring& URL, const std::wstring& params, Buffer& outBuff, DWORD& codePage)
{
	// copy from "http://msdn.microsoft.com/en-us/library/aa384270"
	DWORD dwSize = 0;
	DWORD dwDownloaded = 0;
	BOOL  bResults = FALSE;
	HINTERNET  hSession = NULL, hConnect = NULL, hRequest = NULL;

	// Use WinHttpOpen to obtain a session handle.
	hSession = WinHttpOpen( L"WinHTTP Example/1.0",  
		WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
		WINHTTP_NO_PROXY_NAME, 
		WINHTTP_NO_PROXY_BYPASS, 0 );

	// Specify an HTTP server.
	if( hSession )
		hConnect = WinHttpConnect(hSession, URL.c_str(), //L"www.microsoft.com",
		mPort, 0 );

	// Create an HTTP request handle.
	if( hConnect )
		hRequest = WinHttpOpenRequest(hConnect, 
			L"GET", 
			params.c_str(),
			NULL,
			WINHTTP_NO_REFERER, 
			WINHTTP_DEFAULT_ACCEPT_TYPES, 
			0/*WINHTTP_FLAG_SECURE*/);

	// Send a request.
	if( hRequest )
		bResults = WinHttpSendRequest( hRequest,
		WINHTTP_NO_ADDITIONAL_HEADERS, 0,
		WINHTTP_NO_REQUEST_DATA, 0, 
		0, 0 );

	DWORD error = GetLastError();

	// End the request.
	if (bResults)
		bResults = WinHttpReceiveResponse( hRequest, NULL );

	// Get the code page.
	if (bResults)
	{
		dwSize = sizeof(DWORD);
		WinHttpQueryOption(
			hRequest,
			WINHTTP_OPTION_CODEPAGE,
			&codePage,
			&dwSize);
	}

	// Keep checking for data until there is nothing left.
	if (bResults)
	{
		do 
		{
			// Check for available data.
			dwSize = 0;
			if (!WinHttpQueryDataAvailable( hRequest, &dwSize) || dwSize == 0)
			{
				printf( "Error %u in WinHttpQueryDataAvailable.\n", GetLastError( ) );
				break;
			}

			// Allocate space for the buffer.
			size_t pos = outBuff.size();
			outBuff.resize(outBuff.size() + dwSize);

			if (!WinHttpReadData( hRequest, (LPVOID)&outBuff[pos], dwSize, &dwDownloaded))
			{
				printf( "Error %u in WinHttpReadData.\n", GetLastError( ) );
				break;
			}
		} while( dwSize > 0 );
	}

	// Report any errors.
	if (!bResults)
		printf( "Error %d has occurred.\n", GetLastError( ) );

	// Close any open handles.
	if (hRequest) WinHttpCloseHandle(hRequest);
	if (hConnect) WinHttpCloseHandle(hConnect);
	if (hSession) WinHttpCloseHandle(hSession);

	return bResults != 0;
}

bool HttpClient::Execute(const std::wstring& URL, const std::wstring& params, std::wstring& outString)
{
	DWORD codePage = 0;
	Buffer requestBuff;
	if (!Execute(URL, params, requestBuff, codePage))
		return false;

	// push the terminate tocken.
	if (requestBuff.back() != '\0')
		requestBuff.push_back('\0');

	// get the required char number.
	int wideCharNum = MultiByteToWideChar(codePage, 0, &requestBuff[0], requestBuff.size(), NULL, 0);
	if (wideCharNum <= 0)
	{
		printf( "Error %u in MultiByteToWideChar.\n", GetLastError( ) );
	}

	// allocate the size.
	outString.resize(wideCharNum);

	// convert data base on the code page.
	int ret = MultiByteToWideChar(codePage, 0, &requestBuff[0], requestBuff.size(), &outString[0], outString.size());
	if (ret <= 0)
	{
		printf( "Error %u in MultiByteToWideChar.\n", GetLastError( ) );
		return false;
	}

	outString.resize(ret);

	return true;
}

bool HttpClient::Execute(HttpRequest* request)
{
	HttpRequest::ParamMap params;
	request->DefineParams(params);
	params[L"method"] = request->GetApiName();

	// pending the session if we have.
	if (!mSession.empty())
		params[L"session"] = mSession;

	// TODO: build sign.

	// build the full URL path.
	bool hasParam = false;
	std::wstringstream ss;
	for (HttpRequest::ParamMap::const_iterator it(params.begin()), end(params.end()); it != end; it++)
	{
		const std::wstring& name = it->first;
		const std::wstring& value = it->second;
		if (name.empty() || value.empty())
			continue;

		ss << (hasParam ? L"&" : L"?");
		ss << name << L"=" << value;
		hasParam = true;
	}
	
	HttpResponse* response = request->getResponse();
	if (response == NULL)
	{
		std::wstring body;
		return Execute(mURL, ss.str(), body);
	}

	// post the request.
	response->mError = false;
	if (!Execute(mURL, ss.str(), response->mBody))
	{
		response->mError = true;
		return false;
	}

	// parse the return buffer to response.
	if (!response->Parse())
	{
		response->mError = true;
		return false;
	}

	return true;
}
